package javaLearning.数据库连接池.ConnectionPool.Pool;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;

/**
 * @author : K k
 * @date : 17:58 2020/10/11
 * 相当于工厂Factory
 * 数据库连接池（需要导入数据源的数据库连接池）
 * 管理所有连接，包括分配、释放、监控连接以及连接池状态
 * 类说明 :友元类，包内可见，不提供给客户程序直接访问。
 */
public class ConnectionPool implements IConnectionPool {
    private static final Logger log = Logger.getLogger(ConnectionPool.class);

    private DataSource propertyBean = null;

    //连接池可用状态
    private Boolean isActive = true;
    // 空闲连接池 。由于List读写频繁，使用LinkedList存储比较合适
    private LinkedList<Connection> freeConnections = new LinkedList<Connection>();
    // 活动连接池。活动连接数 <= 允许最大连接数(maxConnections)
    private LinkedList<Connection> activeConnections = new LinkedList<Connection>();
    //当前线程获得的连接,获取本地线程变量
    private ThreadLocal<Connection> currentConnection = new ThreadLocal<Connection>();
    //定时检查的定时线程池
    ScheduledExecutorService ses;

    //构造方法无法返回null，所以取消掉。在下面增加了CreateConnectionPool静态方法。
    private ConnectionPool() {
        super();
    }

    //不通过实例化，而是通过静态方式获取实例对象
    public static ConnectionPool CreateConnectionPool(DataSource propertyBean) {
        ConnectionPool connpool = new ConnectionPool();
        connpool.propertyBean = propertyBean;

        //加载驱动
        //在多节点环境配置下，因为在这里无法判断驱动是否已经加载,可能会造成多次重复加载相同驱动。
        //因此加载驱动的动作，挪到connectionManager管理类中去实现了。
        /*try {
            Class.forName(connpool.propertyBean.getDriverName());
            log.info("加载JDBC驱动"+connpool.propertyBean.getDriverName()+"成功");
        } catch (ClassNotFoundException e) {
            log.info("未找到JDBC驱动" + connpool.propertyBean.getDriverName() + "，请引入相关包");
            return null;
        }*/

        //基本点2、始使化时根据配置中的初始连接数创建指定数量的连接
        for (int i = 0; i < connpool.propertyBean.getInitConnections(); i++) {
            try {
                //创建数据库初始连接线程
                Connection conn = connpool.NewConnection();
                //添加连接池中的空闲队列中
                if (conn != null) {
                    connpool.freeConnections.add(conn);
                }
            } catch (SQLException | ClassNotFoundException e) {
                log.error(connpool.propertyBean.getNodeName() + "节点连接池初始化失败");
                return null;
            }
        }
        connpool.isActive = true;
        return connpool;
    }

    /**
     * 创建一个新的连接,此时改为存放connection的代理对象
     *
     * @return 数据库连接对象
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    private Connection NewConnection() throws ClassNotFoundException,
            SQLException {
        Connection conn = null;
        try {
            if (this.propertyBean != null) {
                //若实例化了DataSource
                //Class.forName(this.propertyBean.getDriverName());
                //实例化连接对象
                conn = DriverManager.getConnection(this.propertyBean.getUrl(),
                        this.propertyBean.getUsername(), this.propertyBean.getPassword());
            } else {
                log.warn("DataSource is not available -> 配置源未配置完成");
            }
        } catch (SQLException e) {
            throw new SQLException(e);
        }

        if (conn != null) {
            ConProxyHandler handler = new ConProxyHandler(this, conn);
            Connection proxy_conn = (Connection) Proxy.newProxyInstance(
                    ConnectionPool.class.getClassLoader(),
                    new Class[]{Connection.class}, handler);
            //System.out.println("生成代理类：" + conn_return);
            return proxy_conn;
        }

        return conn;
    }

    /**
     * 同步锁 获取连接对象
     *
     * @param times 记录调用次数 用于计算重连
     * @return Connection
     */
    @Override
    public synchronized Connection getConnection(Long times) {
        Connection conn = null;
        if (this.getActiveNum() < this.propertyBean.getMaxConnections()) {
            //分支1：当前连接未到达最大连接池连接数
            //基本点3、在连接池没有达到最大连接数之前，如果有可用的空闲连接就直接使用空闲连接，如果没有，就创建新的连接。
            if (this.getFreeNum() > 0) {
                //分支1.1 如果空闲队列中有空闲连接，就直接从空闲连接池中获取
                log.info("分支1.1：如果空闲池中有连接，就从空闲池中直接获取");
                conn = this.freeConnections.pollFirst();
                //连接闲置久了也会超时，因此空闲池中的有效连接会越来越少，需要另一个进程进行扫描监测，不断保持一定数量的可用连接。
                //在下面定义了checkFreepools的TimerTask类，在checkPool()方法中进行调用。

                //基本点5、由于数据库连接闲置久了会超时关闭，因此需要连接池采用机制保证每次请求的连接都是有效可用的。
                try {
                    if (this.isValidConnection(conn)) {
                        //若为有效连接，则加入到活动队列（从空闲队列到活动队列）
                        this.activeConnections.add(conn);
                        this.freeConnections.remove(conn);
                        //添加为当前线程的连接，意味着当前线程可以通过get方法获取该线程的conn用于close
                        //TODO 此处可以使用本地线程变量来解决释放资源问题
                        currentConnection.set(conn);
                    } else {
                        //若为无效状态，则重新获取
                        conn = getConnection(0L); //同步方法是可重入锁 synchronized
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            } else {
                // 分支1.2：如果空闲池中无可用连接，就创建新的连接
                log.info("分支1.2：如果空闲池中无可用连接，就创建新的连接");
                try {
                    //创建新线程
                    conn = this.NewConnection();
                    if (conn != null) {
                        this.activeConnections.add(conn);
                        this.currentConnection.set(conn);
                    }
                } catch (SQLException | ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        } else {
            // 分支2：当前已到达最大连接数
            // 基本点4、当连接池中的活动连接数达到最大连接数，新的请求进入等待状态，直到有连接被释放。
            log.info("分支2：当前连接池已到达最大连接数 ");
            //TODO 超时时间需要修改
            long startTime = System.currentTimeMillis();
            //进入等待状态。等待被notify(),notifyALL()唤醒或者超时自动苏醒
            try {
                System.out.println(Thread.currentThread().getName() + "正在等待连接 尝试连接次数是" + times/this.propertyBean.getConninterval());
                this.wait(this.propertyBean.getConninterval());
            } catch (InterruptedException e) {
                log.error("线程被打断");
            }
            //若线程超时前被唤醒并成功获取连接，就不会走到return null。
            //若线程超时前没有获取连接，则返回null。
            //如果timeout设置为0，就无限重连。
            Long waitTime = System.currentTimeMillis() - startTime + times;
            if (this.propertyBean.getTimeout() != 0) {
                System.out.println(Thread.currentThread().getName() + "等待的时间是 " + waitTime);
                if (waitTime > this.propertyBean.getTimeout()) {
                    //超过了连接超时事件，连接作废，返回空
                    System.out.println(Thread.currentThread().getName() + "等待超时了！！获取连接请求作废");
                    return null;
                }
            }
            //唤醒继续重连
            conn = this.getConnection(waitTime);
        }

        return conn;
    }

    /**
     * 检测连接是否有效
     *
     * @return Boolean
     * @params 连接对象
     */
    private boolean isValidConnection(Connection conn) throws SQLException {
        try {
            if (conn == null || conn.isClosed()) {
                return false;
            } else {
                //从队列中移除
                for (int i = 0; i < this.freeConnections.size(); i++) {
                    if (this.freeConnections.get(i).hashCode() == conn.hashCode()) {
                        this.freeConnections.remove(this.freeConnections.get(i));
                    }
                }
                for (int i = 0; i < this.activeConnections.size(); i++) {
                    if (this.activeConnections.get(i).hashCode() == conn.hashCode()) {
                        this.activeConnections.remove(this.activeConnections.get(i));
                    }
                }
            }
        } catch (SQLException e) {
            throw new SQLException(e);
        }
        return true;
    }

    @Override
    public Connection getCurrentConnecton() {
        Connection conn = currentConnection.get();
        try {
            if (!isValidConnection(conn)) {
                conn = this.getConnection(0l);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return conn;
    }

    @Override
    public synchronized void releaseConn(Connection conn) throws SQLException {
        log.info(Thread.currentThread().getName() + "关闭连接：activeConnections.remove:" + conn);
        //模拟结束耗时
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //从活动队列中删除
        for (int i = 0; i < this.activeConnections.size(); i++) {
            if (conn.hashCode() == this.activeConnections.get(i).hashCode()) {
                this.activeConnections.remove(i);
            }
        }
        //从本地连接删除
        this.currentConnection.remove();
        //活动连接池删除的连接，相应的加到空闲连接池中
        try {
            if (isValidConnection(conn)) {
                //若为有效连接
                freeConnections.add(conn);
            } else {
                freeConnections.add(this.NewConnection());
            }
        } catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        //若有阻塞的线程则唤醒getConnection()中等待的线程
        this.notifyAll();
    }

    @Override
    public void destroy() {
        // 将线程池中的连接全部释放
        for (Connection conn : this.freeConnections) {
            try {
                if (this.isValidConnection(conn)) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        for (Connection conn : this.activeConnections) {
            try {
                if (this.isValidConnection(conn)) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        // 清空队列，并将连接池状态置为false
        this.isActive = false;
        this.freeConnections.clear();
        this.activeConnections.clear();
        this.ses.shutdown();
    }

    @Override
    public boolean isActive() {
        return this.isActive;
    }

    @Override
    public void checkPool() {
        final String nodename = this.propertyBean.getNodeName();
        //定时任务线程池
        ses = Executors.newScheduledThreadPool(2);
        //功能一：开启一个定时器线程输出状态 1秒钟检查一次
        ses.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(nodename + "空闲连接数：" + getFreeNum());
                System.out.println(nodename + "活动连接数：" + getActiveNum());
            }
        }, 1, 1, TimeUnit.SECONDS);

        //功能二：开启一个定时器线程，监测并维持空闲池中的最小连接数
        ses.scheduleAtFixedRate(new CheckFreepools(this), 1, 5, TimeUnit.SECONDS);
    }

    @Override
    public int getActiveNum() {
        return this.activeConnections.size();
    }

    @Override
    public int getFreeNum() {
        return this.freeConnections.size();
    }


    //基本点6、连接池内部要保证指定最小连接数量的空闲连接
    //由于每次释放连接，都会将活动连接放回空闲队列中，所以空闲队列会一直膨胀，需要检测是否空闲超时，是则销回
    class CheckFreepools extends TimerTask {
        private ConnectionPool connpool = null;

        public CheckFreepools(ConnectionPool cp) {
            this.connpool = cp;
        }

        @Override
        public void run() {
            if (this.connpool != null && this.connpool.isActive()) {
                int poolsTotalNum = connpool.getFreeNum()
                        + connpool.getActiveNum();
                int subNum = connpool.propertyBean.getMinConnections() - poolsTotalNum;
                if (subNum > 0) {
                    //大于0则说明当前连接数小于最小要求,需要补充
                    System.out.println(connpool.propertyBean.getNodeName()
                            + "扫描并维持空闲池中的最小连接数，需补充" + subNum + "个连接");
                    for (int i = 0; i < subNum; i++) {
                        try {
                            Connection conn = connpool.NewConnection();
                            //添加连接到空闲队列
                            connpool.freeConnections.add(conn);
                        } catch (SQLException | ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    /**
     * Connection的代理类
     */
    class ConProxyHandler implements InvocationHandler {
        private Connection conn;
        private ConnectionPool cp;

        public ConProxyHandler(ConnectionPool cp, Connection conn) {
            this.cp = cp;
            this.conn = conn;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            if (method.getName().equals("close")) {
                /*TODO 错误点！！！ 此时释放的是原始对象，而空闲队列和活动队列中的对象都是代理对象,导致在释放资源的时候
                     不能将同一对象移除
                     解决方案：调用ThreadLocal来处理代理connection的释放
                 */
                Connection connection = currentConnection.get();
                cp.releaseConn(connection);
                return null;
            } else {
                //如果不是调用close方法，就原样处理
                return method.invoke(this.conn, args);
            }
        }

    }

}
